﻿using System;
using System.Collections.Generic;
using System.Xml;
using SdmxMl.Helper;
using SdmxMl.Manager;
using SdmxMl.Common;

namespace SdmxMl.Structure
{
    #region Summary

    /// <summary>
    /// AttributeType describes the structure of attributes declared in the key family.
    /// If the codelist attribute is not used, then the attribute is uncoded. You may
    /// use the TextFormat element to specify constraints on the value of the uncoded
    /// attribute. The concept attribute contains the name of a concept. The codelist
    /// attribute supplies the id value of a codelist. The attachmentLevel attribute
    /// indicates the level to which the attribute is attached in time-series formats
    /// (GenericData, CompactData, and UtilityData formats). The assignmentStatus
    /// attribute indicates whether a value must be provided for the attribute when
    /// sending documentation along with the data. The AttachmentGroup element is
    /// included only when the attribute is attached at the Group level, to indicate
    /// which declared group or groups the attribute may be attached to. For each
    /// such group, an AttachmentGroup element should appear, with the content of
    /// the element being the name of the group. The AttachmentMeasure element is
    /// similar, indicating for cross-sectional formats which declared measure or
    /// measures the attribute attached at the observation level may be attached to.
    /// The isTimeFormat attribute indicates that the attribute represents the
    /// concept of time format (typically a mandatory series-level attribute with
    /// a codelist representation taken from ISO 8601). For key families not used
    /// to structure cross-sectional formats, this element may be ommitted. Each
    /// such element contains the name of the declared measure. The attributes
    /// crossSectionalAttachDataSet, crossSectionalAttachGroup,
    /// crossSectionalAttachSection, and crossSectionalAttachObservation indicate
    /// what the attachment level or levels are for cross-sectional data formats,
    /// and may be ommitted if the key family will not be used to structure them.
    /// A value of "true" indicates that it is permissible to provide a value for
    /// the attribute at the specified level within the structure. Note that all
    /// groups in cross-sectional formats are replaced by a generic group which
    /// has any values for time and frequency, and allows any group-level attributes
    /// to be attached to it. An attribute which is an Entity attribute has a true
    /// value for the isEntityAttribute attribute - you may have either one Entity
    /// dimension or one Entity Attribute in a key family; a non-observational time
    /// has a true value for isNonObservationalTimeAttribute; and a Count attribute
    /// has a true value for the isCountAttribute attribute. The attributes
    /// isFrequencyAttribute and isEntityAttribute are mutually exclusive - that is,
    /// only one of them may have a "true" value for any instance of the Attribute
    /// element. The definitions and limits on representation of each attribute type
    /// are as follows: Frequency attribute describes the period between observations,
    /// and is coded; Count attributes are represented by values which are sequential,
    /// incrementing numbers - representations are always of the Increment or Count
    /// type; Entity attributes describe the subject of the data set - they can be
    /// coded or represented in any other form; Non-Observational Time attributes
    /// must have a representation which contains a time; Identity attributes may be
    /// coded or uncoded, but must be represented by a scheme which refers to the
    /// identifiers of external entities. Any given instance of an attribute may only
    /// have a true value for this set of attributes, and if so may not have a true
    /// value for the isTimeFormat attribute.
    /// </summary>
    #endregion
    public class AttributeType: BaseDimensionType
    {
        #region Internal Enums

        public enum AttachmentLevelType
        {
            DataSet,
            Group,
            Series,
            Observation,
        }

        public enum AssignmentStatusType
        {
            Mandatory,
            Conditional,
        }

        public enum AttributeKindType
        {
            Unspecified,
            TimeFormat,
            Entity,
            Count,
            NonObservationalTime,
            Identity,
            Frequency,
        }
        #endregion

        #region Properties

        private CrossSectionalAttachKind _crossAttachKind;
        public CrossSectionalAttachKind CrossSectionalAttach
        {
            get { return _crossAttachKind; }
            set { _crossAttachKind = value; }
        }

        private AttributeKindType _attributeKind;
        public AttributeKindType AttributeKind
        {
            get { return _attributeKind; }
            set { _attributeKind = value; }
        }


        private AttachmentLevelType _attachmentLevel;
        public AttachmentLevelType AttachmentLevel
        {
            get { return _attachmentLevel; }
            set { _attachmentLevel = value; }
        }

        private AssignmentStatusType _assignmentStatus;
        public AssignmentStatusType AssignmentStatus
        {
            get { return _assignmentStatus; }
            set { _assignmentStatus = value; }
        }

        private AttachmentGroupIdList _attachmentGroupIdList;
        public AttachmentGroupIdList AttachmentGroupList
        {
            get { return _attachmentGroupIdList; }
        }

        private AttachmentMeasureCodeList _attachmentMeasureCodeList;
        public AttachmentMeasureCodeList AttachmentMeasureList
        {
            get { return _attachmentMeasureCodeList; }
        }

        // SDMX 21
        // Relationship for dimensions or primaryMeasure
        public List<string> AttributeRelationship { get; set; }

        #endregion

        #region Construction

        public AttributeType()
            : base()
        {
            Init();
        }

        private void Init()
        {
            _attributeKind = AttributeKindType.Unspecified;
            _attachmentLevel = AttachmentLevelType.Observation;
            _attachmentMeasureCodeList = new AttachmentMeasureCodeList();
            _attachmentGroupIdList = new AttachmentGroupIdList();
            AttributeRelationship = new List<string>();
        }

        public AttributeType(XmlNode nodeArtefact, SdmxNamespaceManager nsmgr) 
            : base(nodeArtefact, nsmgr)
        {
            Init();

            string str = Xmlhelper.GetAtt(nodeArtefact, "assignmentStatus");
            if (str.Length > 0)
            {
                if (Enum.IsDefined(typeof(AssignmentStatusType), str))
                    _assignmentStatus = (AssignmentStatusType)Enum.Parse(typeof(AssignmentStatusType), str, true);
                else
                    throw new ApplicationException("Unrecognised Assignement status: " + str);
            }
            else
                _assignmentStatus = AssignmentStatusType.Conditional;

            if (nsmgr.VersionSdmx == SdmxVersion.SDMX_20)
            {
                str = Xmlhelper.GetAtt(nodeArtefact, "attachmentLevel");
                if (Enum.IsDefined(typeof(AttachmentLevelType), str))
                    _attachmentLevel = (AttachmentLevelType)Enum.Parse(typeof(AttachmentLevelType), str, true);
                else
                    throw new ApplicationException("Unrecognised Attachement level: " + str);



                foreach (AttributeKindType k in Enum.GetValues(typeof(AttributeKindType)))
                {
                    string kind = Xmlhelper.GetAtt(nodeArtefact, "is" + k.ToString() + "Attribute");
                    if (kind.ToLower() == "true")
                    {
                        _attributeKind = k;
                        break;
                    }
                }
                if (Xmlhelper.GetAtt(nodeArtefact, "isTimeFormat").ToLower() == "true")
                    _attributeKind = AttributeKindType.TimeFormat;

                string query = "./structure:AttachmentMeasure";
                XmlNodeList nodeList = nodeArtefact.SelectNodes(query, nsmgr);
                if (nodeList.Count > 0)
                    foreach (XmlNode nd in nodeList)
                        AttachmentMeasureList.Add(nd.InnerText);

                query = "./structure:AttachmentGroup";
                nodeList = nodeArtefact.SelectNodes(query, nsmgr);
                if (nodeList.Count > 0)
                    foreach (XmlNode nd in nodeList)
                        AttachmentGroupList.Add(nd.InnerText);
            }
            else //SDMX 21
            {
                string query = "./structure:AttributeRelationship/structure:Group/Ref";
                XmlNode ndGroup = nodeArtefact.SelectSingleNode(query, nsmgr);
                if (ndGroup != null)
                {
                    AttachmentGroupList.Add(Xmlhelper.GetAtt(ndGroup, "id"));
                    _attachmentLevel = AttachmentLevelType.Group;
                }

                // Dim
                query = "./structure:AttributeRelationship/structure:Dimension";
                XmlNodeList ndDimList = nodeArtefact.SelectNodes(query, nsmgr);
                if (ndDimList.Count > 0)
                {
                    foreach (XmlNode ndDim in ndDimList)
                    {
                        XmlNode ndRef = ndDim.SelectSingleNode("./Ref");
                        if (ndRef != null)
                        {
                            AttributeRelationship.Add(Xmlhelper.GetAtt(ndRef, "id"));
                            if (_attachmentLevel != AttachmentLevelType.Group)
                                _attachmentLevel = AttachmentLevelType.Series;
                        }
                    }
                }
                query = "./structure:AttributeRelationship/structure:AttachmentGroup";
                ndDimList = nodeArtefact.SelectNodes(query, nsmgr);
                if (ndDimList.Count > 0)
                {
                    foreach (XmlNode ndDim in ndDimList)
                    {
                        XmlNode ndRef = ndDim.SelectSingleNode("./Ref");
                        if (ndRef != null)
                        {
                            AttachmentGroupList.Add(Xmlhelper.GetAtt(ndRef, "id"));
                            _attachmentLevel = AttachmentLevelType.Group;
                        }
                    }
                }

                // Primary Measure
                query = "./structure:AttributeRelationship/structure:PrimaryMeasure/Ref";
                XmlNode ndPrimary = nodeArtefact.SelectSingleNode(query, nsmgr);
                if (ndPrimary != null)
                {
                    _attachmentLevel = AttachmentLevelType.Observation;
                }

            }
        }
        #endregion

        #region XML Serialize

        public void WriteXml(XmlTextWriter xw, string prefix, string tag)
        {
            xw.WriteStartElement(prefix, tag, null);

            xw.WriteAttributeString("assignmentStatus", _assignmentStatus.ToString());
            WriteReferences(xw, prefix);

            if (SdmxNamespaceManager.SdmxTarget == SdmxVersion.SDMX_20)
            {
                // Required
                xw.WriteAttributeString("attachmentLevel", _attachmentLevel.ToString());

                if (_attributeKind == AttributeKindType.TimeFormat)
                    xw.WriteAttributeString("isTimeFormat", "true");

                if (_crossAttachKind != CrossSectionalAttachKind.None)
                    xw.WriteAttributeString("crossSectionalAttach" + _crossAttachKind.ToString(), "true");

                if (_attributeKind != AttributeKindType.Unspecified && _attributeKind != AttributeKindType.TimeFormat)
                    xw.WriteAttributeString("is" + _attributeKind.ToString() + "Attribute", "true");

                // Text Format
                textFormat.WriteXml(xw, prefix, "TextFormat");

                if (_attachmentLevel == AttachmentLevelType.Observation)
                    foreach (string s in _attachmentMeasureCodeList)
                        Xmlhelper.WriteElementIfExist(xw, prefix, "AttachmentMeasure", s);

                if (_attachmentLevel == AttachmentLevelType.Group)
                    foreach (string s in _attachmentGroupIdList)
                        Xmlhelper.WriteElementIfExist(xw, prefix, "AttachmentGroup", s);

                // Support new SDMX 2.1 ID attribute as Annotations
                WriteAnnotationsAndId(xw, prefix);

            }
            else // SDMX 2.1
            {
                xw.WriteStartElement(prefix, "AttributeRelationship", null);

                if (_attachmentLevel == AttachmentLevelType.DataSet)
                    xw.WriteElementString(prefix, "None", null, string.Empty);

                if (_attachmentLevel == AttachmentLevelType.Group && AttributeRelationship.Count == 0 &&
                    _attachmentGroupIdList.Count > 0)
                {
                    xw.WriteStartElement(prefix, "Group", null);
                    xw.WriteStartElement("Ref");
                    Xmlhelper.WriteAttributeIfExist(xw, "id", _attachmentGroupIdList[0]);
                    xw.WriteEndElement();
                    xw.WriteEndElement();
                }
                else if (AttributeRelationship.Count > 0 && _attachmentLevel != AttachmentLevelType.Observation)
                {
                    foreach (string s in AttributeRelationship)
                    {
                        xw.WriteStartElement(prefix, "Dimension", null);
                        xw.WriteStartElement("Ref");
                        Xmlhelper.WriteAttributeIfExist(xw, "id", s);
                        xw.WriteEndElement();
                        xw.WriteEndElement();
                    }
                    if (_attachmentGroupIdList.Count > 0)
                    {
                        foreach (string s in _attachmentGroupIdList)
                        {
                            xw.WriteStartElement(prefix, "AttachmentGroup", null);
                            xw.WriteStartElement("Ref");
                            Xmlhelper.WriteAttributeIfExist(xw, "id", s);
                            xw.WriteEndElement();
                            xw.WriteEndElement();
                        }
                    }
                }
                else if (_attachmentLevel == AttachmentLevelType.Observation)
                {
                    xw.WriteStartElement(prefix, "PrimaryMeasure", null);
                    xw.WriteStartElement("Ref");
                    Xmlhelper.WriteAttributeIfExist(xw, "id", "OBS_VALUE");
                    xw.WriteEndElement();
                    xw.WriteEndElement();
                }
                xw.WriteEndElement(); // end AttributeRelationship
            }

            xw.WriteEndElement();
        }
        #endregion

    }


    /// <summary>
    /// A list but for one unique element
    /// </summary>
    public class AttributeTypeList : BaseDimensionTypeList
    {
        #region Parametrized C'tor

        public AttributeTypeList(KeyFamilyType dsd)
            : base(dsd)
        {
        }
        #endregion

        #region Override 

        /// <summary>Type for enumeration in role</summary>
        /// <returns></returns>
        protected override Type GetRoleEnumType()
        {
            return typeof(AttributeType.AttributeKindType);
        }
        #endregion

        #region Public Methods

        public override List<object> GetRowObjects(BaseDimensionType bd)
        {
            List<object> lo = new List<object>(12);
            cType = ReferenceManager.LocateConcept(bd.ConceptSchemeInfo, bd.ConceptInfo.Id);
            string conceptName = cType == null ? string.Empty : cType.Name.ToString();

            clType = ReferenceManager.LocateCodelist(bd.CodelistInfo);
            string codelistName = clType == null ? string.Empty : bd.CodelistInfo.Version + " - " + clType.Name.ToString();
            lo.Add(cType != null);
            lo.Add(System.DBNull.Value); // Icon
            lo.Add(GetAttKind(bd));
            lo.Add(bd.ConceptInfo.Id);
            lo.Add((bd as AttributeType).AttachmentLevel.ToString());
            lo.Add(GetCrossAttachText(bd));
            lo.Add(conceptName);
            lo.Add(GetReferedCodelistId(bd));
            lo.Add((bd as AttributeType).AssignmentStatus.ToString());
            lo.Add(GetReferedTextFormat(bd));
            lo.Add(string.Empty);
            lo.Add(string.Empty);

            return lo;
        }

        public override string GetCrossAttachText(BaseDimensionType bd)
        {
                return (bd as AttributeType).CrossSectionalAttach.ToString();
        }

        public AttributeType.AttributeKindType GetAttKind(BaseDimensionType bd)
        {
                return (bd as AttributeType).AttributeKind;

        }

        public override BaseDimensionType NewItem()
        {
            return new AttributeType();
        }
        #endregion

        #region String representation

        public override string ToString()
        {
            return "Attributes";
        }
        #endregion
    }

    public class AttachmentGroupIdList : List<string>
    {
       
    }

    public class AttachmentMeasureCodeList : List<string>
    {
        /*
        public bool ContainsCodeId(string id)
        {
            bool contain = false;

            foreach (string code in this)

            return contain;
        }
         * */
    }


}
